package net.huazai.interceptor;

import io.jsonwebtoken.Claims;
import lombok.extern.slf4j.Slf4j;
import net.huazai.entity.LoginUser;
import net.huazai.enums.BizCodes;
import net.huazai.utils.ApiResult;
import net.huazai.utils.CommonUtil;
import net.huazai.utils.JwtUtil;
import org.apache.commons.lang3.StringUtils;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

/**
 * @author 11987
 */
@Slf4j
public class LoginInterceptor implements HandlerInterceptor {

    /**
     * 这里我们使用 ThreadLocal 传递用户 token 信息
     * 全称 thread local variable（线程局部变量）功用非常简单，使用场合主要解决多线程中数据因并发产生不一致问题。
     * ThreadLocal 为每一个线程都提供了变量的副本，使得每个线程在某时间访问到的并不是同一个对象，
     * 这样就隔离了多个线程对数据的数据共享，这样的结果是耗费了内存，但大大减少了线程同步所带来性能消耗，
     * 也减少了线程并发控制的复杂度。
     * 总结：同个线程共享数据，它可以用来每个线程内需要独立保存信息，方便同个线程的其他方法获取该信息的场景。
     */
    public static ThreadLocal<LoginUser> LOGIN_USER_THREAD_LOCAL = new ThreadLocal<>();

    /**
     * 登录拦截器
     * 1、解密JWT
     * 2、传递登录用户信息，这里我们使用 LOGIN_USER_THREAD_LOCAL 进行传递
     * @param request 请求对象
     * @param response 响应对象
     * @param handler 处理器
     * @return 拦截结果
     */
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response
            , Object handler){
        // 获取 accessToken
        String accessToken = request.getHeader("token");
        if (accessToken == null) {
            // 如果请求头中没有 token，则从参数中进行获取
            accessToken = request.getParameter("token");
        }

        // 判断 token 是否为空
        if (StringUtils.isNotBlank(accessToken)) {
            // 1、解密 token
            Claims claims = JwtUtil.checkJwt(accessToken);
            if (claims == null) {
                // 解密失败，登录过期，提示账号未登录
                CommonUtil.sendResponse(response, ApiResult.doResult(BizCodes.USER_ACCOUNT_UNLOGIN));
                log.info("登录拦截器--解密 token 失败：{}", ApiResult.doResult(BizCodes.USER_ACCOUNT_UNLOGIN));
                return false;
            }

            // 解密成功，获取用户相关信息
            long userId = Long.parseLong(claims.get("id").toString());
            String headAvatar = (String) claims.get("head_avatar");
            String name = (String) claims.get("name");
            String mail = (String) claims.get("mail");

            LoginUser loginUser =new LoginUser();
            loginUser.setId(userId);
            loginUser.setName(name);
            loginUser.setHeadAvatar(headAvatar);
            loginUser.setMail(mail);

            // 2、使用 LOGIN_USER_THREAD_LOCAL 进行传递登录用户信息
            LOGIN_USER_THREAD_LOCAL.set(loginUser);
            return true;
        }

        // token 为空，提示账号未登录
        CommonUtil.sendResponse(response, ApiResult.doResult(BizCodes.USER_ACCOUNT_UNLOGIN));
        log.info("登录拦截器--token 为空：{}", ApiResult.doResult(BizCodes.USER_ACCOUNT_UNLOGIN));
        return false;
    }

    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
        HandlerInterceptor.super.postHandle(request, response, handler, modelAndView);
    }

    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
        // 清理 ThreadLocal，防止内存泄漏
        // 在请求完成后必须调用 remove() 方法，避免在线程池复用线程时出现数据污染和内存泄漏
        LOGIN_USER_THREAD_LOCAL.remove();
        HandlerInterceptor.super.afterCompletion(request, response, handler, ex);
    }
}
